#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/sched.h>
#include<linux/slab.h>


#define DEVICE_NAME "concurrctrl"
#define BUFFER_SIZE 0x1000
#define DEVICE_MAJOR 200
#define DEVICE_COUNT 1



/* ==============================================并发控制==================================== */
/*
主要解决多进程共享资源的并发访问问题：
1.并发和竞态的概念和发生场合
2.编译乱序和执行乱序的问题以及内存屏障
3.一些并发控制机制：互斥锁、自旋锁、读写锁、信号量、原子操作
4.并发控制后的globalmem 驱动
*/



/*
1.并发和竞态的概念和发生场合
并发：多个进程在一段时间内同时存在
竞态：多个进程在一段时间内同时访问同一资源，导致资源状态不一致
发生场合：在多核CPU下，多个进程可能同时访问同一资源，导致竞态；解决途径就是保证对共享资源的访问是互斥的
*/

/*
2.编译乱序和执行乱序的问题以及内存屏障
编译乱序：编译器为了优化代码，可能会改变指令的执行顺序；防止编译优化的问题，可以使用内存屏障，保证编译器不会改变指令的执行顺序
        内存屏障：编译器指令，告诉编译器不要改变指令的执行顺序(__asm__ __volatile__("memory" ::: "memory"))
        在对硬件寄存器进行操作的时候，volatile 是很有用的，防止编译器优化之后，导致时序不对
执行乱序：CPU为了提高执行效率，可能会改变指令的执行顺序
        像CPU里面排好的指令，可能后面指令的执行结果，被提前到前面指令的执行结果，导致指令的执行顺序不对
        读写屏障：使用读写屏障，保证读写指令的执行顺序
        linux中的作用于寄存器的读写屏障主要有：__iormb()、__iowmb()、
*/


/*
3.一些并发控制的机制：
屏蔽中断：这个实际上就是为了屏蔽进程之间的抢占，防止进程切换，导致竞态
        local_irq_disable();  //屏蔽中断，同时保存中断状态
        local_irq_enable();  //恢复中断，同时恢复中断状态
互斥锁：这个是为了防止多个进程同时访问共享资源，导致竞态
        mutex_t lock;  //定义一个互斥锁
        mutex_init(&lock);  //初始化互斥锁
        mutex_lock(&lock);  //获取互斥锁，它会导致睡眠，因此不能在中断上下文中使用
        mutex_trylock(&lock);  //尝试获取互斥锁，它不会导致睡眠，因此可以在中断上下文中使用
        mutex_lock_interruptible(&lock);  //获取互斥锁，它会导致睡眠，但是可以被打断
        mutex_unlock(&lock);  //释放互斥锁
自旋锁：这个是为了防止多个进程同时访问共享资源，导致竞态，但是自旋锁会一直占用CPU，直到获取到锁；
        spin_lock_t lock;  //定义一个自旋锁
        spin_lock_init(&lock);  //初始化自旋锁
        spin_lock(&lock);  //获取自旋锁,如果获取到，就立即返回；如果获取不到锁，会一直自旋，直到获取到锁；(阻塞)
        spin_trylock(&lock);  //尝试获取自旋锁，如果获取到，就立即返回；如果获取不到锁，就立即返回，不会自旋
        spin_unlock(&lock);  //释放自旋锁

读写锁：这个是为了防止多个进程同时访问共享资源，导致竞态，但是读写锁可以允许多个进程同时读，但是只允许一个进程写
        rwlock_t lock;  //定义一个读写锁
        rwlock_init(&lock);  //初始化读写锁

        read_lock(&lock);  //获取读锁，
        read_trylock(&lock);  //尝试获取读锁
        read_lock_irqsave(&lock, flags);  //获取读锁，同时屏蔽中断
        read_lock_irq(&lock);  //获取读锁，同时屏蔽中断
        read_unlock(&lock);  //释放读锁

        write_lock(&lock);  //获取写锁
        write_trylock(&lock);  //尝试获取写锁
        write_lock_irqsave(&lock, flags);  //获取写锁，同时屏蔽中断
        write_lock_irq(&lock);  //获取写锁，同时屏蔽中断
        write_unlock(&lock);  //释放写锁
        write_unlock_irqrestore(&lock, flags);  //释放写锁，同时恢复中断
        write_unlock_irq(&lock);  //释放写锁，同时恢复中断

信号量：这个是为了防止多个进程同时访问共享资源，导致竞态，但是信号量可以允许多个进程同时访问共享资源，但是只允许一个进程写
        struct semaphore sem;  //定义一个信号量
        sema_init(&sem, 1);  //初始化信号量，初始值为1
        down(&sem);  //获取信号量，它会导致睡眠，因此不能在中断上下文中使用
        down_interruptible(&sem);  //获取信号量，它会导致睡眠，因此不能在中断上下文中使用，但是可以被打断
        down_trylock(&sem);  //尝试获取信号量，它不会导致睡眠，因此可以在中断上下文中使用
        up(&sem);  //释放信号量

原子操作：这个是为了防止多个进程同时访问共享资源，导致竞态，但是原子操作可以保证操作的原子性，不会被打断
整形原子操作：
        void atiomic_set(atomic_t *v, int val)  //设置原子变量的值为val
        atimoc_t v = ATOMIC_INIT(0)  //初始化原子变量
        int atomic_read(atomic_t *v)  //读取原子变量的值
        void atomic_inc(atomic_t *v)  //原子变量的值加1
        void atomic_dec(atomic_t *v)  //原子变量的值减1
        void atomic_add(int i, atomic_t *v)  //原子变量的值加i
        void atomic_sub(int i, atomic_t *v)  //原子变量的值减i
位原子操作：
        void set_bit(int nr, void *addr)  //设置addr的第nr位为1
        void clear_bit(int nr, void *addr)  //设置addr的第nr位为0
        void change_bit(int nr, void *addr)  //设置addr的第nr位为1，如果为1，则设置为0，如果为0，则设置为1
        int test_bit(int nr, void *addr)  //测试addr的第nr位是否为1，如果是1，则返回1，否则返回0

*/


struct globalmem_dev {
    struct cdev cdev;
    mutex m_mutex;
    unsigned long mem[BUFFER_SIZE];
};

static globalmem_dev *g_dev;
static int globalmem_major = DEVICE_MAJOR;

static int globalmem_open(struct inode *inode, struct file *file)
{
    file->private_data = g_dev;
    return 0;
}

static int globalmem_release(struct inode *inode, struct file *file)
{    
    return 0;
}

static ssize_t globalmem_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    struct globalmem_dev *dev = container_of(file->f_path.dentry->d_inode->i_cdev, struct globalmem_dev, cdev);
    size_t ret = 0;
    unsigned long pos = *ppos;
    uint32_t size = count;
    if(pos >= BUFFER_SIZE) {
        return 0;
    }
    if(pos + size > BUFFER_SIZE) {
        size = BUFFER_SIZE - pos;
    }
    mutex_lock(&dev->m_mutex);
    if(copy_to_user(buf, dev->mem + pos, size)) {
        ret = -EFAULT;
    } else {
        ret = size;
        *ppos += size;
        printk(KERN_INFO "read %lu bytes\n", size);
    }
    mutex_unlock(&dev->m_mutex);
    return ret;
}

static ssize_t globalmem_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    struct globalmem_dev *dev = container_of(file->f_path.dentry->d_inode->i_cdev, struct globalmem_dev, cdev);
    unsigned long pos = *ppos;
    uint32_t size = count;
    int ret = 0;
    if(pos >= BUFFER_SIZE) {
        return 0;
    }
    if(pos + size > BUFFER_SIZE) {
        size = BUFFER_SIZE - pos;
    }
    mutex_lock(&dev->m_mutex);
    if(copy_from_user(dev->mem + pos, buf, size)) {
        ret = -EFAULT;
    }
    else {
        ret = size;
        *ppos += size;
        printk(KERN_INFO "write %lu bytes\n", size);
    }
    mutex_unlock(&dev->m_mutex);
    return ret;
}

static long globalmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct globalmem_dev *dev = file->private_data;
    switch(cmd) {
        case GLOBALMEM_READ:
            break;
        case GLOBALMEM_WRITE:
            break;
        case GLOBALMEM_CLEAR:
            mutex_lock(&dev->m_mutex);
            memset(dev->mem, 0, BUFFER_SIZE);
            mutex_unlock(&dev->m_mutex);
            break;
        default:
            return -EINVAL;
        
    }
    return 0;
}

static lloft globalmem_llseek(struct file *file, loff_t offset, int whence)
{
    struct globalmem_dev *dev = file->private_data;
    loff_t ret = 0;
    switch(whence) {
        case SEEK_SET:
            ret = offset;
            break;
        case SEEK_CUR:
            ret = file->f_pos + offset;
            break;
        case SEEK_END:
            ret = dev->size + offset;
            break;
        default:
            return -EINVAL; 
    }
    return ret;
}



static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = globalmem_open,
    .release = globalmem_release,
    .read = globalmem_read,
    .write = globalmem_write,

}


static int __init globalmem_init(void)
{
    int ret = 0;
    dev_t devno = MKDEV(globalmem_major, 0);
    if(globalmem_major)
        ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
    else
        ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
    if(ret < 0) {
        printk(KERN_ERR "globalmem: can't get major number\n");
        return ret;
    }
    g_dev = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
    if(!g_dev) {
        printk(KERN_ERR "globalmem: can't allocate device\n");
        unregister_chrdev_region(devno, 1);
        return -ENOMEM;
    }
    mutex_init(&g_dev->m_mutex);
    cdev_init(&g_dev->cdev, &fops);
    g_dev->cdev.owner = THIS_MODULE;
    ret = cdev_add(&g_dev->cdev, devno, 1);
    return 0;
}
static void __exit globalmem_exit(void)
{
    cdev_del(&g_dev->cdev);
    unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
    kfree(g_dev);
}

module_init(globalmem_init);
module_exit(globalmem_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjianzhao");
MODULE_DESCRIPTION("Global Memory Driver");





/*
阻塞IO和非阻塞IO：read/write 以及 select/poll/epoll
    阻塞IO：用户进程发起IO操作后，需要等待IO操作完成才能继续执行其他操作，典型的阻塞IO是read/write
    非阻塞IO：用户进程发起IO操作后，不需要等待IO操作完成就可以继续执行其他操作，采用轮询的非阻塞IO是select/poll，采用事件通知的非阻塞IO是epoll
    select: 每次调用select，都需要把fd集合从用户态拷贝到内核态，这个开销在fd很多时会很大
    poll: poll和select实现机制类似，区别在于poll没有最大fd限制，它将用户传入的数组拷贝到内核态，但是不需要在返回时将fd集合拷贝回用户态，因此效率更高
    epoll: epoll通过在内核中维护一个fd的列表来避免重复拷贝，同时epoll还提供了ET（边缘触发）和LT（水平触发）两种模式，LT模式类似于poll，ET模式类似于select，但是ET模式效率更高，
           因为epoll只有在fd状态发生变化时才会通知用户，而select和poll则会在每次调用时都检查所有fd的状态
同步IO和异步IO: 
    同步IO：用户进程发起IO操作后，需要等待IO操作完成才能继续执行其他操作
    异步IO：用户进程发起IO操作后，不需要等待IO操作完成就可以继续执行其他操作，IO操作完成后，内核会通过信号或者回调函数通知用户进程

阻塞IO：一直等待设备可以访问后在访问
非阻塞IO：查询设备是否可以访问；如果设备不可访问，则立即返回，不会等待设备可访问
异步IO：发起IO操作后，不需要等待IO操作完成就可以继续执行其他操作，IO操作完成后，内核会通过信号或者回调函数通知用户进程
*/


/*
    异步IO通知的概念：驱动上异步通知的好处就是，不需要实时监控某一件事情的发生，当有一件事情发生后
                     发起通知事件，然后接收方接收到这个事件的通知，作相应的处理就好了；
    信号：signal是进程间通信的一种方式，用于通知进程某个事件已经发生，进程可以捕获信号并作出相应的处理
    sighandler_t signal(int signum, sighandler_t handler);
    signum: 信号类型
    handler: 信号处理函数

    常见的信号类型：
    SIGINT: 中断信号，通常由Ctrl+C产生
    SIGQUIT: 退出信号，通常由Ctrl+\产生
    SIGKILL: 终止信号，无法被捕获、阻塞或忽略
    SIGALRM: 定时器信号，由alarm函数产生
    SIGCHLD: 子进程状态改变信号，通常由wait函数产生
    SIGIO: IO信号，由ioctl函数产生
    SIGUSR1: 用户自定义信号1
    SIGUSR2: 用户自定义信号2
*/


/*
   中断和定时器：linux的中断和定时器
   中断 ：当硬件设备需要向CPU发出请求时，会向CPU发送一个中断信号，CPU接收到中断信号后，会暂停当前正在执行的程序，转而执行中断处理程序，中断处理程序执行完毕后，CPU会恢复执行被中断的程序
   定时器：定时器是一种硬件设备，用于定时产生中断信号，定时器中断的处理程序会定时执行，用于实现定时任务
   中断： int request_irq(int irq, irq_handler_t handler, const char *name, void *dev_id);
         void free_irq(int irq, void *dev_id);
   定时器： int request_timer(struct timer_list *timer, unsigned long expires, unsigned int data, int flags);

*/

// GPIO 的按键中断程序：

static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
    // 按键中断处理程序
    return IRQ_HANDLED;
}

static int __init button_init(void)
{
    int ret = 0;
    // 申请GPIO引脚
    ret = gpio_request(BUTTON_GPIO, "button");
    if(ret < 0) {
        printk(KERN_ERR "button: can't request GPIO\n");
    }
    // 设置GPIO引脚为输入模式
    gpio_direction_input(BUTTON_GPIO);
    // 注册中断处理程序
    ret = request_irq(BUTTON_IRQ, button_irq_handler, IRQF_TRIGGER_FALLING, "button", NULL);
    if(ret < 0) {
        printk(KERN_ERR "button: can't request IRQ\n");
    }

    return 0;
}
static void __exit button_exit(void)
{
    // 注销中断处理程序
    free_irq(BUTTON_IRQ, NULL);
    // 释放GPIO引脚
    gpio_free(BUTTON_GPIO);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjianzhao");



// 内核定时器程序：
static void timer_callback(struct timer_list *timer)
{
    // 定时器回调函数

}
static int __init timer_init(void)
{
    int ret = 0;
    // 初始化定时器
    init_timer(&timer);
    timer.function = timer_callback;
    timer.expires = jiffies + msecs_to_jiffies(1000); // 1秒后触发定时器
    add_timer(&timer);
    return 0;
}
static void __exit timer_exit(void)
{
    // 删除定时器
    del_timer(&timer);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjianzhao");


/*
重点
    内存和I/O：*******
    想ARM架构的微处理器，现在大部分并不支持IO空间
    内存空间: 可以通过指针进行访问：
        像unsigned char* p = (unsigned char *)0x11223344;
        typedef void (*callback)();
        callback func= (callback)0x11223344;
        func();
    内存管理单元（MMU）：主要辅助系统内存管理
    提供虚拟地址和物理地址映射；
        CPU访问内存时，首先通过MMU将虚拟地址转换为物理地址，然后访问物理地址对应的内存单元（CPU ---虚拟地址---> MMU ---物理地址---> 内存）
    内存访问权限保护和Cache缓存控制
    TLB:转换旁路缓存，用于加速虚拟地址到物理地址的转换
    TWU:页表遍历模块，页表是由操作系统维护在物理内存中，但是页表的遍历查询是由TWU完成的
    cat /proc/slabinfo   可以知道slab的分配和使用情况（内核空间的动态内存申请）

*/

/*
301====需要再看一遍
    1.linux 驱动软件的构架
    2.linux驱动和设备分离的优点
    3.驱动分层，核心层和底层的交互方法
    4.驱动中，主机和外设驱动分离的设计思想
*/
/*
    关于驱动软件的架构： 实际上就是为了解决兼容性的问题；让驱动软件可以适应不同的硬件设备，同时也可以适应不同的操作系统
        比如设备要用到SPI，会通过CPU的SPI寄存器，进行访问；但是不同的CPU，SPI寄存器的地址是不同的，
        所以驱动软件需要针对不同的CPU进行适配，这就需要驱动软件的架构设计；
        需要一个中间层，将不同的CPU操作不同的SPI集成到一起，这样驱动软件只需要和中间层进行交互，
        就可以实现SPI的访问，而不用关心具体的CPU；
    驱动分离的优点：
        1.驱动软件可以适应不同的硬件设备
        2.驱动软件可以适应不同的操作系统
        3.驱动软件可以适应不同的CPU
        4.驱动软件可以适应不同的外设驱动
    
*/


// include/linux/platform_device.h
struct platform_device {
	const char	* name;  // 这个名字用来绑定驱动的
	int		id;  // 设备id
	struct device	dev;  // 具体的设备对象
	u32		num_resources;  // 资源数量
	struct resource	* resource;  // 资源，用来描述该设备的一些资源,像地址啊、中断号等
	const struct platform_device_id	*id_entry;
	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};
// include/linux/platform_device.h
struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	// 下面3个接口是为了实现电源管理方面的功能接口，为低功耗而实现的
	void (*shutdown)(struct platform_device *);  // 关闭
	int (*suspend)(struct platform_device *, pm_message_t state);  // 挂起
	int (*resume)(struct platform_device *); // 重启
	struct device_driver driver;
	const struct platform_device_id *id_table;
};


static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

/*
    具体的操作忽略 ... ... 
*/



#include "linux/sched.h"
#include "linux/types.h"
#include "linux/poll.h"
#include "linux/platform_device.h"
 
#define  GLOBALFIFO_SIZE   (0X1000)
#define  GLOBALFIFO_MAJOR  (231)
#define DEVICE_NUM         (1)

static int globalfifo_init_dev(struct globalfifo_dev *dev, int i)
{
    dev->fifo = kmalloc(GLOBALFIFO_SIZE, GFP_KERNEL);
    dev->rd_ptr = dev->fifo;
    dev->wr_ptr = dev->fifo;
    dev->fifo_len = 0;
    dev->fifo_size = GLOBALFIFO_SIZE;
    dev->f_flags = 0;
    return 0;
}


static int glboalfifo_probe(struct platform_device *pdev)
{
    int i = 0;
    int ret = 0;
    // 1. get the device id
    dev_t devno = MKDEV(globalfifo_major, 0);
#ifdef globalfifo_debug
    printk(KERN_NOTICE "globalfifo_init\n");
#endif
    // 2. register the DEVICE_NUM of the char device
    if (globalfifo_major)
    {
        ret = register_chrdev_region(devno, DEVICE_NUM, "globalfifo");
    }
    else
    {
        // automatic register char device
        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalfifo");
    }
    // check the error code
    if (ret < 0)
    {
        return ret;
    }

    // 3. construct the globalfifo devices structure in the heap
    globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev) * DEVICE_NUM, GFP_KERNEL);
    if (!globalfifo_devp)
    {
        ret = -ENOMEM;
#ifdef globalfifo_debug
    printk(KERN_NOTICE "globalfifo_init = %d\n", __LINE__);
#endif
        goto fail_malloc;
    }

    // initialize the mutex
    mutex_init(&globalfifo_devp->mutex);

    // initialize the write and read wait queue head
    init_waitqueue_head(&globalfifo_devp->r_wait);
    init_waitqueue_head(&globalfifo_devp->w_wait);

    // 4. add the globalfifo decices structure pointer to the kobjct map
    for (i = 0; i < DEVICE_NUM; i++)
    {
         /* 对设备绑定操作 */
        cdev_init(&globalmem_devp[i]->cdev, &globalmem_fops);
        globalmem_devp[i]->cdev.owner = THIS_MODULE;
        /*注册设备*/
        ret = cdev_add(&globalmem_devp[i]->cdev, dev_major, 1);
        if(ret < 0) {
            printk(KERN_INFO "cdev_add failed\n");
        }
    }
    printk("globalfifo_init success\n");
    return 0;

 fail_malloc:
    unregister_chrdev_region(devno, DEVICE_NUM);
    return ret;
}

static int glboalfifo_remove(struct platform_device *pdev)
{
    int i = 0;
#ifdef globalfifo_debug
    printk(KERN_NOTICE "globalfifo_exit\n");
#endif
    // 1. remove the globalfifo structure from teh kobject map
    for (i = 0; i < DEVICE_NUM; i++)
    {
        cdev_del(&(globalfifo_devp + i)->chrdev);
    }

    // 2. free the glboalmem structure in the heap
    kfree(globalfifo_devp);

    // 3. unregister the device id
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), DEVICE_NUM);

    // 4. remove the device id
    printk("globalfifo_exit success\n");
}

static struct platform_driver globalfifo_driver = {
    .driver = {
        .name = "glboalfifo",  // used in devices binding
        .owner = THIS_MODULE,
    },
    .probe = glboalfifo_probe,
    .remove = glboalfifo_remove,
};

//module_init(globalfifo_init)
//module_exit(globalfifo_exit)
module_platform_driver(globalfifo_driver)
// the declaration	of the author
MODULE_AUTHOR("xxxxxxxxxx");
// the declaration of the licence



// arch/arm/mach-<soc名>/mach-<板名>.c
static struct platform_device globalfifo_device = {
	.name = "globalfifo",
	.id = -1,
};
static void __init globalfifo_machine_init(void)
{
	...
	platform_add_devices(globalfifo_device, ARRAY_SIZE(globalfifo_device));
	...
}

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
	const struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
	struct gpio_keys_drvdata *ddata;
	struct device *dev = &pdev->dev;
	struct gpio_keys_platform_data alt_pdata;
	struct input_dev *input;
	int i, error;
	int wakeup = 0;

	if (!pdata) {
		error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
		if (error)
			return error;
		pdata = &alt_pdata;
	}

	ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
			pdata->nbuttons * sizeof(struct gpio_button_data),
			GFP_KERNEL);
	input = input_allocate_device();
	if (!ddata || !input) {
		dev_err(dev, "failed to allocate state\n");
		error = -ENOMEM;
		goto fail1;
	}

	ddata->input = input;
	ddata->n_buttons = pdata->nbuttons;
	ddata->enable = pdata->enable;
	ddata->disable = pdata->disable;
	mutex_init(&ddata->disable_lock);

	platform_set_drvdata(pdev, ddata);
	input_set_drvdata(input, ddata);

	input->name = pdata->name ? : pdev->name;
	input->phys = "gpio-keys/input0";
	input->dev.parent = &pdev->dev;
	input->open = gpio_keys_open;
	input->close = gpio_keys_close;

	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;

	/* Enable auto repeat feature of Linux input subsystem */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {
		const struct gpio_keys_button *button = &pdata->buttons[i];
		struct gpio_button_data *bdata = &ddata->data[i];

		error = gpio_keys_setup_key(pdev, input, bdata, button);
		if (error)
			goto fail2;

		if (button->wakeup)
			wakeup = 1;
	}

	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
	if (error) {
		dev_err(dev, "Unable to export keys/switches, error: %d\n",
			error);
		goto fail2;
	}

	error = input_register_device(input);
	if (error) {
		dev_err(dev, "Unable to register input device, error: %d\n",
			error);
		goto fail3;
	}

	/* get current state of buttons that are connected to GPIOs */
	for (i = 0; i < pdata->nbuttons; i++) {
		struct gpio_button_data *bdata = &ddata->data[i];
		if (gpio_is_valid(bdata->button->gpio))
			gpio_keys_gpio_report_event(bdata);
	}
	input_sync(input);

	device_init_wakeup(&pdev->dev, wakeup);

	return 0;

 fail3:
	sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
 fail2:
	while (--i >= 0)
		gpio_remove_key(&ddata->data[i]);

	platform_set_drvdata(pdev, NULL);
 fail1:
	input_free_device(input);
	kfree(ddata);
	/* If we have no platform_data, we allocated buttons dynamically. */
	if (!pdev->dev.platform_data)
		kfree(pdata->buttons);

	return error;
}













